package org.dtrd.config.wechat.api;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import lombok.SneakyThrows;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.dtrd.config.pay.PayConfig;
import org.dtrd.config.wechat.api.IWechatPayApi.WechatPaySystemApi.PostRequest;
import org.dtrd.config.wechat.config.wechatpay.PayScene;
import org.dtrd.config.wechat.config.WechatUtil;
import org.dtrd.config.wechat.entity.bean.pay.WechatPayConfig;
import org.dtrd.config.wechat.entity.request.wechatpay.SceneInfo;
import org.dtrd.config.wechat.entity.request.wechatpay.WechatPayAttach;

import java.io.ByteArrayOutputStream;
import java.util.*;

/**
 * 微信支付API
 *
 * @author xiao
 */
public interface IWechatPayApi extends IWechatBaseApi {

    /**
     * 微信支付平台API路径
     */
    interface WechatPaySystemApi {

        interface GetRequest {
            /**
             * GET 获取平台证书列表
             * <p>
             * {@link <a href="https://pay.weixin.qq.com/wiki/doc/apiv3/apis/wechatpay5_1.shtml">获取平台证书</a>}
             */
            String CERTIFICATES_LIST = "https://api.mch.weixin.qq.com/v3/certificates";

            /**
             * 支付订单号查询订单
             * {@link <a href="https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_2.shtml">查询订单</a>}
             */
            String QUERY_ORDER_WECHATPAY_NO = "https://api.mch.weixin.qq.com/v3/pay/transactions/id/";

            /**
             * 商户订单号查询订单
             * {@link <a href="https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_2.shtml">查询订单</a>}
             */
            String QUERY_ORDER_SYSTEM_NO = " https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/";
        }

        interface PostRequest {

            /**
             * 预下单接口路径，待拼接支付场景名称{@link PayScene}后使用
             */
            String BASE_PREPAY = "https://api.mch.weixin.qq.com/v3/pay/transactions/";
            /**
             * 根据商户订单号关闭订单
             */
            String CLOSE_ORDER = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/";

        }

    }

    /**
     * 构造预下单请求
     *
     * @param total
     * @param userId
     * @param outTradeNo
     * @param description
     * @param attachStr
     * @return
     */
    default ObjectNode buildRequest(Long total, Integer userId, String outTradeNo, String description, String attachStr) {
        WechatPayAttach attach = new WechatPayAttach();
        attach.setAttach(attachStr);
        attach.setUserId(userId);
        ObjectNode rootNode = new ObjectMapper().createObjectNode();
        rootNode.put("description", description)
                .put("out_trade_no", outTradeNo)
                .put("appid", WechatUtil.getClientId())
                .put("attach", JSON.toJSONString(attach));
        rootNode.putObject("amount")
                .put("total", total);
        return rootNode;
    }

    /**
     * 执行微信POST请求
     *
     * @param url
     * @param request
     * @return
     */
    @SneakyThrows
    default String postRequest(String url, ObjectNode request) {
        WechatPayConfig payConfig = PayConfig.getWechatPayConfig();
        Verifier verifier = CertificatesManager.getInstance().getVerifier(payConfig.getWechatPayMchId());
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(payConfig.getWechatPayMchId(), payConfig.getSerialNo(), PayConfig.getMerchantPrivateKey())
                .withValidator(new WechatPay2Validator(verifier));
        //通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签
        CloseableHttpClient httpClient = builder.build();
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        if (request != null) {
            request.put("mchid", payConfig.getWechatPayMchId())
                    .put("notify_url", payConfig.getWechatPayNotifyUrl());
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.writeValue(bos, request);
            StringEntity entity = new StringEntity(bos.toString("UTF-8"), "UTF-8");
            httpPost.setEntity(entity);
        }
        CloseableHttpResponse response = httpClient.execute(httpPost);
        return EntityUtils.toString(response.getEntity());
    }

    /**
     * 执行微信GET请求
     *
     * @param url
     * @param params
     * @return
     */
    @SneakyThrows
    default String getRequest(String url, Map<String, Object> params) {
        WechatPayConfig payConfig = PayConfig.getWechatPayConfig();
        Verifier verifier = CertificatesManager.getInstance().getVerifier(payConfig.getWechatPayMchId());
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(payConfig.getWechatPayMchId(), payConfig.getSerialNo(), PayConfig.getMerchantPrivateKey())
                .withValidator(new WechatPay2Validator(verifier));
        //通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签
        CloseableHttpClient httpClient = builder.build();
        StringBuilder sb = new StringBuilder();
        sb.append(url);
        sb.append("?mch=").append(payConfig.getWechatPayMchId());
        if (params != null) {
            StringJoiner sj = new StringJoiner("&");
            params.keySet().forEach(key -> sj.add(key).add("=").add(params.get(key) + ""));
            sb.append(sj);
        }
        HttpGet httpGet = new HttpGet(sb.toString());
        httpGet.addHeader("Accept", "application/json");
        httpGet.addHeader("Content-type", "application/json; charset=utf-8");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        return EntityUtils.toString(response.getEntity());
    }

    /**
     * 预下单
     *
     * @param scene
     * @param request
     * @return
     */
    @SneakyThrows
    default String prePay(PayScene scene, ObjectNode request) {
        return postRequest(PostRequest.BASE_PREPAY + scene.getName(), request);
    }

    /**
     * H5预下单
     *
     * @param total
     * @param userId
     * @param outTradeNo
     * @param description
     * @param attach
     * @param sceneInfo
     * @return
     */
    String h5PrePay(Long total, Integer userId, String outTradeNo, String description, String attach, SceneInfo sceneInfo);

    /**
     * app预下单
     *
     * @param total
     * @param userId
     * @param outTradeNo
     * @param description
     * @param attach
     * @return
     */
    String appPrePay(Long total, Integer userId, String outTradeNo, String description, String attach);

    /**
     * JS预下单
     *
     * @param total
     * @param userId
     * @param openId
     * @param outTradeNo
     * @param description
     * @param attach
     * @return
     */
    String jsPrePay(Long total, Integer userId, String openId, String outTradeNo, String description, String attach);

    /**
     * Native预下单
     *
     * @param total
     * @param patientId
     * @param outTradeNo
     * @param description
     * @param attach
     * @param sceneInfo
     * @return
     */
    String nativePrePay(Long total, Integer patientId, String outTradeNo, String description, String attach, SceneInfo sceneInfo);

    /**
     * 根据微信支付号查询订单
     *
     * @param wechatPayNo
     * @return
     */
    String queryOrderByWechatPayNo(String wechatPayNo);

    /**
     * 根据商户订单号查询订单
     *
     * @param systemOrderNo
     * @return
     */
    String queryOrderBySystemOrderNo(String systemOrderNo);

    /**
     * 根据商户订单号关闭订单
     *
     * @param orderNo
     * @return
     */
    String closeOrder(String orderNo);

}
